﻿using System;
using System.IO;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Collections.Generic;
using Sinacor.Infra.Tools.ServiceRegistration.Business;
using System.Configuration;
using System.ServiceModel.Configuration;
using System.Runtime.Serialization;
using System.Xml;

namespace Sinacor.Infra.Tools.ServiceGenerator.Loader
{
    public class AssemblyLoader
    {
        private Assembly _assembly;
        private DateTime _dateChange;

        public void LoadServices(string assemblyFile, DateTime dateChange)
        {
            AssemblyName assemblyRef = new AssemblyName();
            assemblyRef.CodeBase = assemblyFile;
            _assembly = Assembly.Load(assemblyRef);
            string fileName;

            _dateChange = dateChange;

            System.Type[] serviceTypes = LocateServices(_assembly.GetTypes());

            if (serviceTypes.Length > 0)
            {
                fileName = _assembly.Location + ".config";

                if (!File.Exists(fileName))
                    throw new FileNotFoundException(fileName);

                CreateModule(serviceTypes, fileName);
            }
        }

        private void CreateModule(System.Type[] serviceTypes, string fileName)
        {
            ServiceRegistration.Entity.Module entityModule = new ServiceRegistration.Entity.Module();
            ServiceRegistration.Entity.SubModule entitySubModule = new ServiceRegistration.Entity.SubModule();
            ServiceRegistration.Business.Module bizModule = new ServiceRegistration.Business.Module();
            ServiceRegistration.Business.SubModule bizSubModule = new ServiceRegistration.Business.SubModule();

            entitySubModule = GetSubModule(fileName);

            if (entitySubModule != null)
            {
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine(" ");
                Console.WriteLine("Starting Module = {0} And SubModule = {1}", entitySubModule.Module.ModuleName, entitySubModule.SubModuleName);

                entityModule = bizModule.PersistModule(entitySubModule.Module);

                entitySubModule.Module.ModuleId = entityModule.ModuleId;

                entitySubModule = bizSubModule.PersistSubModule(entitySubModule);

                foreach (System.Type serviceType in serviceTypes)
                {
                    CreateServices(serviceType, entitySubModule);
                }
            }
        }

        private void CreateServices(System.Type serviceType, ServiceRegistration.Entity.SubModule submodule)
        {
            ServiceRegistration.Entity.Service entity = new ServiceRegistration.Entity.Service();
            ServiceRegistration.Business.Service biz = new ServiceRegistration.Business.Service();
            ServiceRegistration.Entity.EndPoint endPoint = new ServiceRegistration.Entity.EndPoint();

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Loading Service {0}", serviceType.FullName);

            XmlElement documentation = DocsByReflection.XMLFromType(serviceType);
            string description = string.Empty;

            if (documentation != null && documentation["summary"] != null)
                description = documentation["summary"].InnerText.Trim();

            entity = new ServiceRegistration.Entity.Service() { 
                ServiceName = serviceType.Name,
                Description = description, 
                SubModule = submodule,
                StatusApproval = ServiceRegistration.Entity.StatusApproval.AguardandoAprovacao,
                DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader,
                DateChange = _dateChange
            };

            entity = biz.PersistService(entity);

            CreateEndPoint(endPoint, entity);

            entity.EndPoints.Add(endPoint);

            foreach (MethodInfo methodInfo in serviceType.GetMethods())
            {
                CreateMethods(methodInfo, entity);
            }
        }

        private void CreateEndPoint(ServiceRegistration.Entity.EndPoint endPointInfo, ServiceRegistration.Entity.Service service)
        {
            ServiceRegistration.Entity.EndPoint entity = new ServiceRegistration.Entity.EndPoint();
            ServiceRegistration.Business.EndPoint biz = new ServiceRegistration.Business.EndPoint();

            entity.Protocol = new Sinacor.Infra.Tools.ServiceRegistration.Entity.Protocol() { ProtocolName = "Tcp" };
            entity.ContractName = "I" + service.ServiceName; 
            entity.Service = service;
            entity.DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader;

            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("Loading EndPoint {0}", entity.ContractName);
            
            endPointInfo = biz.PersistEndPoint(entity);
        }

        private void CreateMethods(MethodInfo methodInfo, ServiceRegistration.Entity.Service service)
        {
            ServiceRegistration.Entity.Method entityMethod = new ServiceRegistration.Entity.Method();
            ServiceRegistration.Business.Method bizMethod = new ServiceRegistration.Business.Method();
            ServiceRegistration.Entity.Type entityType = new ServiceRegistration.Entity.Type();
            ServiceRegistration.Business.Type bizType = new ServiceRegistration.Business.Type();
            ServiceRegistration.Business.Attribute bizAttribute = new ServiceRegistration.Business.Attribute();

            if (methodInfo.IsPublic && methodInfo.IsFinal)
            {
                Console.ForegroundColor = ConsoleColor.Green;          
                Console.WriteLine("Loading Method {0}", methodInfo.Name);

                XmlElement documentation = DocsByReflection.XMLFromMember(methodInfo);
                string description = string.Empty;
                string tables = string.Empty;
                string returns = string.Empty;

                if (documentation != null)
                {
                    if (documentation["summary"] != null)
                        description = documentation["summary"].InnerText.Trim();

                    if (documentation["tables"] != null)
                        tables = documentation["tables"].InnerText.Trim();

                    if (documentation["returns"] != null)
                        returns = documentation["returns"].InnerText.Trim();
                }

                entityType = new ServiceRegistration.Entity.Type() { FullTypeName = GetTypeFullName(methodInfo.ReturnType), Description = returns, DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader, DateChange = _dateChange };
                
                entityType = bizType.PersistType(entityType);

                CreateTypeChilds(methodInfo.ReturnType, entityType);
                
                entityMethod = new ServiceRegistration.Entity.Method() { 
                    MethodName = methodInfo.Name,
                    Description = description, 
                    Service = service, 
                    ReturnType = entityType, 
                    IsReturnCollection = methodInfo.ReturnType.IsArray || methodInfo.ReturnType.IsGenericType,
                    RelatedTables = tables,
                    StatusApproval = ServiceRegistration.Entity.StatusApproval.AguardandoAprovacao,
                    DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader,
                    DateChange = _dateChange
                };

                entityMethod = bizMethod.PersistMethod(entityMethod);

                foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
                {
                    CreateParameters(parameterInfo, entityMethod, documentation);
                }
            }
        }

        private void CreateParameters(ParameterInfo parameterInfo, ServiceRegistration.Entity.Method method, XmlElement documentationMethod)
        {
            ServiceRegistration.Entity.Parameter entityParameter = new ServiceRegistration.Entity.Parameter();
            ServiceRegistration.Business.Parameter bizParameter = new ServiceRegistration.Business.Parameter();
            ServiceRegistration.Entity.Type entityType = new ServiceRegistration.Entity.Type();
            ServiceRegistration.Business.Type bizType = new ServiceRegistration.Business.Type();
            ServiceRegistration.Business.Attribute bizAttribute = new ServiceRegistration.Business.Attribute();

            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine("Loading Parameter {0}", parameterInfo.Name);

            XmlElement documentation = DocsByReflection.XMLFromType(parameterInfo.ParameterType);
            string descriptionType = string.Empty;
            string descriptionParameter = string.Empty;

            if (documentation != null)
            {
                if (documentation["summary"] != null)
                    descriptionType = documentation["summary"].InnerText.Trim();

                if (documentationMethod != null)
                {
                    XmlNode node = documentationMethod.SelectSingleNode(String.Format("param[@name='{0}']", parameterInfo.Name));

                    if (node != null)
                        descriptionParameter = node.InnerText.Trim();
                }
            }

            entityType = new ServiceRegistration.Entity.Type() { FullTypeName = GetTypeFullName(parameterInfo.ParameterType), Description = descriptionType, DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader, DateChange = _dateChange };

            entityType = bizType.PersistType(entityType);

            CreateTypeChilds(parameterInfo.ParameterType, entityType);
            
            entityParameter = new ServiceRegistration.Entity.Parameter() { 
                ParameterName = parameterInfo.Name,
                Description = string.IsNullOrEmpty(descriptionType) ? descriptionParameter : descriptionType,
                Method = method, Type = entityType,
                IsCollection = parameterInfo.ParameterType.IsArray || parameterInfo.ParameterType.IsGenericType,
                DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader,
                DateChange = _dateChange
            };

            entityParameter = bizParameter.PersistParameter(entityParameter);
        }

        private void CreateTypeChilds(System.Type type, ServiceRegistration.Entity.Type entityParentType)
        {
            ServiceRegistration.Entity.Attribute entityAttribute = new ServiceRegistration.Entity.Attribute();
            ServiceRegistration.Business.Attribute bizAttribute = new ServiceRegistration.Business.Attribute();
            ServiceRegistration.Entity.Type entityType = new ServiceRegistration.Entity.Type();
            ServiceRegistration.Business.Type bizType = new ServiceRegistration.Business.Type();

            if (type.IsGenericType)
                type = _assembly.GetType(GetTypeFullName(type));

            if (type != null)
            {
                if (type.IsEnum)
                {
                    foreach (FieldInfo fieldInfo in type.GetFields())
                    {
                        if (!fieldInfo.IsDefined(typeof(EnumMemberAttribute), false)) continue;

                        XmlElement documentation = DocsByReflection.XMLFromMember(fieldInfo);
                        string description = string.Empty;

                        if (documentation != null && documentation["summary"] != null)
                            description = documentation["summary"].InnerText.Trim();

                        entityType = new ServiceRegistration.Entity.Type() { FullTypeName = "System.Int32", DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader, DateChange = _dateChange };

                        entityType = bizType.PersistType(entityType);

                        entityAttribute = new ServiceRegistration.Entity.Attribute() {
                            AttributeName = fieldInfo.Name,
                            ParentTypeId = entityParentType.TypeId,
                            Description = description,
                            Type = entityType,
                            DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader,
                            DateChange = _dateChange
                        };

                        entityAttribute = bizAttribute.PersistAttribute(entityAttribute);
                    }
                }
                else
                {
                    foreach (PropertyInfo propertyInfo in type.GetProperties())
                    {
                        if (!propertyInfo.IsDefined(typeof(DataMemberAttribute), false)) continue;

                        XmlElement documentationProperty = DocsByReflection.XMLFromMember(propertyInfo);
                        XmlElement documentationPropertyType = DocsByReflection.XMLFromType(propertyInfo.PropertyType);
                        string descriptionProperty = string.Empty;
                        string descriptionPropertyType = string.Empty;

                        if (documentationProperty != null && documentationProperty["summary"] != null)
                            descriptionProperty = documentationProperty["summary"].InnerText.Trim();

                        if (documentationPropertyType != null && documentationPropertyType["summary"] != null)
                            descriptionPropertyType = documentationPropertyType["summary"].InnerText.Trim();

                        entityType = new ServiceRegistration.Entity.Type() { FullTypeName = GetTypeFullName(propertyInfo.PropertyType), Description = descriptionPropertyType, DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader, DateChange = _dateChange };

                        entityType = bizType.PersistType(entityType);

                        entityAttribute = new ServiceRegistration.Entity.Attribute() {
                            AttributeName = propertyInfo.Name,
                            ParentTypeId = entityParentType.TypeId,
                            Description = descriptionProperty,
                            Type = entityType,
                            DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader,
                            DateChange = _dateChange
                        };

                        entityAttribute = bizAttribute.PersistAttribute(entityAttribute);

                        CreateTypeChilds(propertyInfo.PropertyType, entityType);
                    }
                }               
            }
        }

        protected ServiceRegistration.Entity.SubModule GetSubModule(string configurationFileName)
        {
            Uri uri;
            string[] names;
            Configuration config;
            ServiceModelSectionGroup group;
            ServiceRegistration.Entity.SubModule submodule = null;
            ExeConfigurationFileMap configurationFileMap = new ExeConfigurationFileMap();

            configurationFileMap.ExeConfigFilename = configurationFileName;
            config = ConfigurationManager.OpenMappedExeConfiguration(configurationFileMap, ConfigurationUserLevel.None);
            group = ServiceModelSectionGroup.GetSectionGroup(config);

            if (group.Services.Services.Count > 0)
            {
                foreach (BaseAddressElement address in group.Services.Services[0].Host.BaseAddresses)
                {
                    uri = new Uri(address.BaseAddress);
                    if (uri.Scheme == Uri.UriSchemeNetTcp)
                    {
                        names = address.BaseAddress.Split(".".ToCharArray());

                        if (names.Length >= 6)
                        {
                            submodule = new ServiceRegistration.Entity.SubModule() {
                                Module = new ServiceRegistration.Entity.Module() { ModuleName = names[3].ToString(), ModuleInitials = " ", DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader, DateChange = _dateChange },
                                SubModuleName = names[4].ToString(),
                                Port = uri.Port,
                                DataOrigin = ServiceRegistration.Entity.DataOrigin.Loader,
                                DateChange = _dateChange
                            };
                        }
                    }
                }
            }

            return submodule;
        }

        private string GetTypeFullName(System.Type type)
        {
            System.Type[] genericArguments = null;
            string name = string.Empty;

            genericArguments = type.GetGenericArguments();

            if (genericArguments.Length > 0)
            {
                if (!genericArguments[0].IsGenericType)
                    name = genericArguments[0].FullName;
                else
                    name = GetTypeFullName(genericArguments[0]);
            }
            else
                name = type.FullName;

            return name;
        }

        private System.Type[] LocateServices(System.Type[] types)
        {
            List<System.Type> services = new List<System.Type>();
            foreach (System.Type type in types)
            {
                if (type.IsClass && DefineServiceContract(type))
                {
                    services.Add(type);
                }
            }
            return services.ToArray();
        }

        private bool DefineServiceContract(System.Type type)
        {
            return
                CheckForServiceContractAttribute(type) ||
                DefineServiceContract(type.GetInterfaces());
              }

        private bool DefineServiceContract(System.Type[] types)
        {
            foreach (System.Type type in types)
            {
                if (DefineServiceContract(type))
                    return true;
            }
            return false;
        }

        private bool CheckForServiceContractAttribute(System.Type type)
        {
            return type.IsDefined(typeof(ServiceContractAttribute), false);
        }
    }
}
